package com.vision.sales.sv.serv;

import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.jasperreports.engine.JRDataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import ar.com.fdvs.dj.domain.DJCalculation;

import com.vision.core.cm.exception.RpcException;
import com.vision.core.sv.db.CustomSessionFactoryBean;
import com.vision.core.sv.db.mgr.DataManager;
import com.vision.core.sv.db.mgr.ISimpleCriteria;
import com.vision.core.sv.db.mgr.SimpleCriteria;
import com.vision.core.sv.serv.BaseRpcServlet;
import com.vision.core.sv.serv.util.ServletUtils;
import com.vision.core.sv.util.DateUtils;
import com.vision.sales.cm.data.SalesPermissions;
import com.vision.sales.cm.data.TransactionStatus;
import com.vision.sales.cm.db.data.Customer;
import com.vision.sales.cm.db.data.SalesInvoice;
import com.vision.sales.sv.db.mgr.CustomerManager;
import com.vision.sales.sv.db.mgr.SalesInvoiceManager;
import com.vision.sales.sv.report.AbstractDataSource;
import com.vision.sales.sv.report.CustomerAgingRow;
import com.vision.sales.sv.report.Report;
import com.vision.sales.sv.report.ReportWriter;

/**
 * Servlet for printing Customer Aging report.
 * 
 * This servlet is accessible via the /print/outstanding_receivables path in the URL (see web.xml)
 * 
 * GET parameters:
 * <ul>
 * 	<li>title - the title that will appear in the report
 * 	<li>date - the as of. Format: yyyyMMdd-yyyyMMdd</li>
 * 	<li>companies - optional comma-separated list of company ids indicating which companies to include. If not specified, all companies will be included.</li>
 * 	<li>customers - optional comma-separated list of customer ids indicating which customers to include. If not specified, all customers will be included.</li>
 * </ul>
 * 
 * Example:
 * <pre>/print/customer_receivables?title=Customer%20Aging&date=20121231&companies=1,2&customers=5,10,22</pre>
 * 
 * @author Mark
 */
@Service("customer_aging")
public class PrintCustomerAgingServlet extends BaseRpcServlet {
	
	private static final long serialVersionUID = 1L;
	
	private static final Logger log = LoggerFactory.getLogger(PrintCustomerAgingServlet.class);

	@Autowired
	@Qualifier("customerManager")
	private DataManager<Customer> customerManager;
	
	@Autowired
	@Qualifier("salesInvoiceManager")
	private DataManager<SalesInvoice> invoiceManager;
	
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		try {
			ServletUtils.checkPermissions(SalesPermissions.ViewOutstandingReceivablesReport);
		} catch (RpcException e) {
			resp.sendError(HttpServletResponse.SC_FORBIDDEN, "You are not authorized to perform this operation.");
			return;
		} 
		
		// Parameters --
		log.debug("Parsing parameters from URL...");
		CustomerAgingParams params = new CustomerAgingParams(req);
		if (params.error != null) {
			resp.sendError(HttpServletResponse.SC_BAD_REQUEST, params.error);
			return;
		}
		
		try {
			DateFormat fnameDateFormat = new SimpleDateFormat("yyyyMMdd");
			DateFormat titleDateFormat = new SimpleDateFormat("MMM d, yyyy");
			
			// Build the Report
			log.debug("Building report...");
			Report report = new Report();
			report.setFilename("Customer Aging " + fnameDateFormat.format(params.date));
			report.setTitle("Customer Aging as of " + titleDateFormat.format(params.date));

			// Columns and Grouping
			report.addStringColumn("customer", "Customer", 40, null);
			for (String term : CustomerAgingRow.TERMS)
				report.addDoubleColumn(term, term, 20, "#,##0.00", DJCalculation.SUM);
			
			report.addDoubleColumn("total", "Total", 20, "#,##0.00", DJCalculation.SUM);
			
			// Data Source
			log.debug("Creating data source...");
			report.setDataSource(getDataSource(params));
			
			// Print and Write to Stream
			log.debug("Writing report to stream...");
			ReportWriter.write(report, resp);
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "The server was unable to process the request.");
		}
	}
	
	private JRDataSource getDataSource(CustomerAgingParams params) {
		// XXX @Autowired doesn't seem to work, thus this patch
		customerManager = new CustomerManager();
		customerManager.setSessionFactory(CustomSessionFactoryBean.getFactory());
		
		// Customers
		List<Customer> customers = customerManager.getAll();
		
		// Invoices
		List<SalesInvoice> salesInvoices = getPostedSalesInvoices(params);
		
		Map<Long, CustomerAgingRow> map = new LinkedHashMap<Long, CustomerAgingRow>();
		for (Customer customer : customers) {
			map.put(customer.getId(), new CustomerAgingRow(customer, new HashMap<String, Double>()));
		}
		
		for (SalesInvoice invoice : salesInvoices) {
			Customer customer = invoice.getCustomer();
			CustomerAgingRow row = map.get(customer.getId());
			if (row == null) {
				map.put(customer.getId(), new CustomerAgingRow(invoice.getCustomer(), new HashMap<String, Double>()));
			}
			
			long diff = DateUtils.dayDifference(invoice.getDate(), params.date);
			log.trace("{} vs. {} = {}", new Object[]{ invoice.getDate(), params.date, diff });
			
			String term = CustomerAgingRow.TERMS[CustomerAgingRow.TERMS.length - 1];
			
			if (diff <= 30L) {
				term = CustomerAgingRow.TERMS[0];
			} else if (diff <= 60L) {
				term = CustomerAgingRow.TERMS[1];
			} else if (diff <= 90L) {
				term = CustomerAgingRow.TERMS[2];
			} else {
				
			}
			
			Double value = row.getBalancesMap().get(term);
			if (value == null)
				value = invoice.getBalance();
			else
				value += invoice.getBalance();
			
			row.getBalancesMap().put(term, value);
		}
		
		for (CustomerAgingRow row : map.values())
			row.computeTotal();
				
		List<CustomerAgingRow> list = new ArrayList<CustomerAgingRow>(map.values());
		Collections.sort(list);
				
		JRDataSource ds = new AbstractDataSource<CustomerAgingRow>(list) {
			@Override
			public Object getValue(CustomerAgingRow object, String fieldName) {
				if ("customer".equals(fieldName)) {
					return object.getCustomer().getName();
				}
				
				return object.get(fieldName);
			}
		};
		return ds;
	}

	@SuppressWarnings("unchecked")
	protected List<SalesInvoice> getPostedSalesInvoices(CustomerAgingParams params) {
		// XXX @Autowired doesn't seem to work, thus this patch
		invoiceManager = new SalesInvoiceManager();
		invoiceManager.setSessionFactory(CustomSessionFactoryBean.getFactory());
		
		ISimpleCriteria crit = SimpleCriteria.forClass(SalesInvoice.class);

		// Invoice Status = POSTED
		crit.eq("status", TransactionStatus.POSTED);

		// Invoice Date <= date
		crit.leq("date", DateUtils.endOfDay(params.date));

		// filter by specified Company Ids
		if (params.companyIds != null && !params.companyIds.isEmpty())
			crit.in("company.id", params.companyIds);

		// filter by specified Customer Ids
		if (params.customerIds != null && !params.customerIds.isEmpty())
			crit.in("customer.id", params.customerIds);
		
		// Only include invoices with balance > 0
		crit.gt("balance", 0d);
		
		// ascendingly sort by Date
		crit.asc("date");
		
		log.debug("Retrieving sales invoices...");
		List<SalesInvoice> invoices = invoiceManager.getTemplate().findByCriteria(crit.getDetached());
		return invoices;
	}
	
	// Inner classes %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
	
	static class CustomerAgingParams extends SalesReportParams {
		
		static final DateFormat yyyyMMdd = new SimpleDateFormat("yyyyMMdd");
		Date date =  new Date();
		
		public CustomerAgingParams(HttpServletRequest req) {
			super(req);
			
			setDefaultTitle("Customer Aging");
			
			try {
				date = yyyyMMdd.parse(req.getParameter("date"));
			} catch (Exception e) {
				log.debug(e.getMessage());
			}
		}
		
	}

}
